// ┌────────────────────────────────────────────────────────────────────┐ \\
// │ F R E E B O A R D                                                  │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Copyright © 2013 Jim Heising (https://github.com/jheising)         │ \\
// │ Copyright © 2013 Bug Labs, Inc. (http://buglabs.net)               │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Licensed under the MIT license.                                    │ \\
// └────────────────────────────────────────────────────────────────────┘ \\

// Jquery plugin to watch for attribute changes
(function ($) {
  function isDOMAttrModifiedSupported() {
    var p = document.createElement('p');
    var flag = false;

    if (p.addEventListener) {
      p.addEventListener('DOMAttrModified', function () {
        flag = true
      }, false);
    }
    else if (p.attachEvent) {
      p.attachEvent('onDOMAttrModified', function () {
        flag = true
      });
    }
    else {
      return false;
    }

    p.setAttribute('id', 'target');

    return flag;
  }

  function checkAttributes(chkAttr, e) {
    if (chkAttr) {
      var attributes = this.data('attr-old-value');

      if (e.attributeName.indexOf('style') >= 0) {
        if (!attributes['style']) {
          attributes['style'] = {};
        } //initialize
        var keys = e.attributeName.split('.');
        e.attributeName = keys[0];
        e.oldValue = attributes['style'][keys[1]]; //old value
        e.newValue = keys[1] + ':' + this.prop("style")[$.camelCase(keys[1])]; //new value
        attributes['style'][keys[1]] = e.newValue;
      }
      else {
        e.oldValue = attributes[e.attributeName];
        e.newValue = this.attr(e.attributeName);
        attributes[e.attributeName] = e.newValue;
      }

      this.data('attr-old-value', attributes); //update the old value object
    }
  }

  //initialize Mutation Observer
  var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

  $.fn.attrchange = function (o) {

    var cfg = {
      trackValues: false,
      callback: $.noop
    };

    //for backward compatibility
    if (typeof o === "function") {
      cfg.callback = o;
    }
    else {
      $.extend(cfg, o);
    }

    if (cfg.trackValues) { //get attributes old value
      $(this).each(function (i, el) {
        var attributes = {};
        for (var attr, i = 0, attrs = el.attributes, l = attrs.length; i < l; i++) {
          attr = attrs.item(i);
          attributes[attr.nodeName] = attr.value;
        }

        $(this).data('attr-old-value', attributes);
      });
    }

    if (MutationObserver) { //Modern Browsers supporting MutationObserver
      /*
       Mutation Observer is still new and not supported by all browsers.
       http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/1622.html
       */
      var mOptions = {
        subtree: false,
        attributes: true,
        attributeOldValue: cfg.trackValues
      };

      var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (e) {
          var _this = e.target;

          //get new value if trackValues is true
          if (cfg.trackValues) {
            /**
             * @KNOWN_ISSUE: The new value is buggy for STYLE attribute as we don't have
             * any additional information on which style is getting updated.
             * */
            e.newValue = $(_this).attr(e.attributeName);
          }

          cfg.callback.call(_this, e);
        });
      });

      return this.each(function () {
        observer.observe(this, mOptions);
      });
    }
    else if (isDOMAttrModifiedSupported()) { //Opera
      //Good old Mutation Events but the performance is no good
      //http://hacks.mozilla.org/2012/05/dom-mutationobserver-reacting-to-dom-changes-without-killing-browser-performance/
      return this.on('DOMAttrModified', function (event) {
        if (event.originalEvent) {
          event = event.originalEvent;
        } //jQuery normalization is not required for us
        event.attributeName = event.attrName; //property names to be consistent with MutationObserver
        event.oldValue = event.prevValue; //property names to be consistent with MutationObserver
        cfg.callback.call(this, event);
      });
    }
    else if ('onpropertychange' in document.body) { //works only in IE
      return this.on('propertychange', function (e) {
        e.attributeName = window.event.propertyName;
        //to set the attr old value
        checkAttributes.call($(this), cfg.trackValues, e);
        cfg.callback.call(this, e);
      });
    }

    return this;
  }
})(jQuery);

(function (jQuery) {

  jQuery.eventEmitter = {
    _JQInit: function () {
      this._JQ = jQuery(this);
    },
    emit: function (evt, data) {
      !this._JQ && this._JQInit();
      this._JQ.trigger(evt, data);
    },
    once: function (evt, handler) {
      !this._JQ && this._JQInit();
      this._JQ.one(evt, handler);
    },
    on: function (evt, handler) {
      !this._JQ && this._JQInit();
      this._JQ.bind(evt, handler);
    },
    off: function (evt, handler) {
      !this._JQ && this._JQInit();
      this._JQ.unbind(evt, handler);
    }
  };

}(jQuery));

var freeboard = (function () {
  var datasourcePlugins = {};
  var widgetPlugins = {};

  var freeboardUI = new FreeboardUI();
  var theFreeboardModel = new FreeboardModel(datasourcePlugins, widgetPlugins, freeboardUI);

  var jsEditor = new JSEditor();
  var valueEditor = new ValueEditor(theFreeboardModel);
  var pluginEditor = new PluginEditor(jsEditor, valueEditor);

  var developerConsole = new DeveloperConsole(theFreeboardModel);

  var currentStyle = {
    values: {
      "font-family": '"HelveticaNeue-UltraLight", "Helvetica Neue Ultra Light", "Helvetica Neue", sans-serif',
      "color": "#d3d4d4",
      "font-weight": 100
    }
  };

  ko.bindingHandlers.pluginEditor = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      var options = ko.unwrap(valueAccessor());

      var types = {};
      var settings = undefined;
      var title = "";

      if (options.type == 'datasource') {
        types = datasourcePlugins;
        title = "数据源";
      }
      else if (options.type == 'widget') {
        types = widgetPlugins;
        title = "窗口组件";
      }
      else if (options.type == 'pane') {
        title = "面板";
      }

      $(element).click(function (event) {
        if (options.operation == 'delete') {
          var phraseElement = $('<p>您确定要删除这个 ' + title + '?</p>');
          new DialogBox(phraseElement, "确定删除", "是", "否", function () {

            if (options.type == 'datasource') {
              theFreeboardModel.deleteDatasource(viewModel);
            }
            else if (options.type == 'widget') {
              theFreeboardModel.deleteWidget(viewModel);
            }
            else if (options.type == 'pane') {
              theFreeboardModel.deletePane(viewModel);
            }

          });
        }
        else {
          var instanceType = undefined;

          if (options.type == 'datasource') {
            if (options.operation == 'add') {
              settings = {};
            }
            else {
              instanceType = viewModel.type();
              settings = viewModel.settings();
              settings.name = viewModel.name();
            }
          }
          else if (options.type == 'widget') {
            if (options.operation == 'add') {
              settings = {};
            }
            else {
              instanceType = viewModel.type();
              settings = viewModel.settings();
            }
          }
          else if (options.type == 'pane') {
            settings = {};

            if (options.operation == 'edit') {
              settings.title = viewModel.title();
              settings.col_width = viewModel.col_width();
            }

            types = {
              settings: {
                settings: [
                  {
                    name: "title",
                    display_name: "标题",
                    type: "text"
                  },
                  {
                    name: "col_width",
                    display_name: "列数",
                    type: "integer",
                    default_value: 1,
                    required: true
                  }
                ]
              }
            }
          }

          pluginEditor.createPluginEditor(title, types, instanceType, settings, function (newSettings) {
            if (options.operation == 'add') {
              if (options.type == 'datasource') {
                var newViewModel = new DatasourceModel(theFreeboardModel, datasourcePlugins);
                theFreeboardModel.addDatasource(newViewModel);

                newViewModel.name(newSettings.settings.name);
                delete newSettings.settings.name;

                newViewModel.settings(newSettings.settings);
                newViewModel.type(newSettings.type);
              }
              else if (options.type == 'widget') {
                var newViewModel = new WidgetModel(theFreeboardModel, widgetPlugins);
                newViewModel.settings(newSettings.settings);
                newViewModel.type(newSettings.type);

                viewModel.widgets.push(newViewModel);

                freeboardUI.attachWidgetEditIcons(element);
              }
            }
            else if (options.operation == 'edit') {
              if (options.type == 'pane') {
                viewModel.title(newSettings.settings.title);
                viewModel.col_width(newSettings.settings.col_width);
                freeboardUI.processResize(false);
              }
              else {
                if (options.type == 'datasource') {
                  viewModel.name(newSettings.settings.name);
                  delete newSettings.settings.name;
                }

                viewModel.type(newSettings.type);
                viewModel.settings(newSettings.settings);
              }
            }
          });
        }
      });
    }
  }

  ko.virtualElements.allowedBindings.datasourceTypeSettings = true;
  ko.bindingHandlers.datasourceTypeSettings = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      processPluginSettings(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
    }
  }

  ko.bindingHandlers.pane = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      if (theFreeboardModel.isEditing()) {
        $(element).css({cursor: "pointer"});
      }

      freeboardUI.addPane(element, viewModel, bindingContext.$root.isEditing());
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      // If pane has been removed
      if (theFreeboardModel.panes.indexOf(viewModel) == -1) {
        freeboardUI.removePane(element);
      }
      freeboardUI.updatePane(element, viewModel);
    }
  }

  ko.bindingHandlers.widget = {
    init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      if (theFreeboardModel.isEditing()) {
        freeboardUI.attachWidgetEditIcons($(element).parent());
      }
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
      if (viewModel.shouldRender()) {
        $(element).empty();
        viewModel.render(element);
      }
    }
  }

  function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), results = regex.exec(location.search);
    return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  }

  $(function () { //DOM Ready
    // Show the loading indicator when we first load
    freeboardUI.showLoadingIndicator(true);

    var resizeTimer;

    function resizeEnd() {
      freeboardUI.processResize(true);
    }

    $(window).resize(function () {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(resizeEnd, 500);
    });

  });

  // PUBLIC FUNCTIONS
  return {
    initialize: function (allowEdit, finishedCallback) {
      ko.applyBindings(theFreeboardModel);

      // Check to see if we have a query param called load. If so, we should load that dashboard initially
      var freeboardLocation = getParameterByName("load");

      if (freeboardLocation != "") {
        $.ajax({
          url: freeboardLocation,
          success: function (data) {
            theFreeboardModel.loadDashboard(data);

            if (_.isFunction(finishedCallback)) {
              finishedCallback();
            }
          }
        });
      }
      else {
        theFreeboardModel.allow_edit(allowEdit);
        theFreeboardModel.setEditing(allowEdit);

        freeboardUI.showLoadingIndicator(false);
        if (_.isFunction(finishedCallback)) {
          finishedCallback();
        }

        freeboard.emit("initialized");
      }
    },
    newDashboard: function () {
      theFreeboardModel.loadDashboard({allow_edit: true});
    },
    loadDashboard: function (configuration, callback) {
      theFreeboardModel.loadDashboard(configuration, callback);
    },
    serialize: function () {
      return theFreeboardModel.serialize();
    },
    setEditing: function (editing, animate) {
      theFreeboardModel.setEditing(editing, animate);
    },
    isEditing: function () {
      return theFreeboardModel.isEditing();
    },
    loadDatasourcePlugin: function (plugin) {
      if (_.isUndefined(plugin.display_name)) {
        plugin.display_name = plugin.type_name;
      }

      // Add a required setting called name to the beginning
      plugin.settings.unshift({
        name: "name",
        display_name: "数据源名称",
        type: "text",
        required: true
      });


      theFreeboardModel.addPluginSource(plugin.source);
      datasourcePlugins[plugin.type_name] = plugin;
      theFreeboardModel._datasourceTypes.valueHasMutated();
    },
    resize: function () {
      freeboardUI.processResize(true);
    },
    loadWidgetPlugin: function (plugin) {
      if (_.isUndefined(plugin.display_name)) {
        plugin.display_name = plugin.type_name;
      }

      theFreeboardModel.addPluginSource(plugin.source);
      widgetPlugins[plugin.type_name] = plugin;
      theFreeboardModel._widgetTypes.valueHasMutated();
    },
    // To be used if freeboard is going to load dynamic assets from a different root URL
    setAssetRoot: function (assetRoot) {
      jsEditor.setAssetRoot(assetRoot);
    },
    addStyle: function (selector, rules) {
      var styleString = selector + "{" + rules + "}";

      var styleElement = $("style#fb-styles");

      if (styleElement.length == 0) {
        styleElement = $('<style id="fb-styles" type="text/css"></style>');
        $("head").append(styleElement);
      }

      if (styleElement[0].styleSheet) {
        styleElement[0].styleSheet.cssText += styleString;
      }
      else {
        styleElement.text(styleElement.text() + styleString);
      }
    },
    showLoadingIndicator: function (show) {
      freeboardUI.showLoadingIndicator(show);
    },
    showDialog: function (contentElement, title, okTitle, cancelTitle, okCallback) {
      new DialogBox(contentElement, title, okTitle, cancelTitle, okCallback);
    },
    getDatasourceSettings: function (datasourceName) {
      var datasources = theFreeboardModel.datasources();

      // Find the datasource with the name specified
      var datasource = _.find(datasources, function (datasourceModel) {
        return (datasourceModel.name() === datasourceName);
      });

      if (datasource) {
        return datasource.settings();
      }
      else {
        return null;
      }
    },
    setDatasourceSettings: function (datasourceName, settings) {
      var datasources = theFreeboardModel.datasources();

      // Find the datasource with the name specified
      var datasource = _.find(datasources, function (datasourceModel) {
        return (datasourceModel.name() === datasourceName);
      });

      if (!datasource) {
        console.log("Datasource not found");
        return;
      }

      var combinedSettings = _.defaults(settings, datasource.settings());
      datasource.settings(combinedSettings);
    },
    getStyleString: function (name) {
      var returnString = "";

      _.each(currentStyle[name], function (value, name) {
        returnString = returnString + name + ":" + value + ";";
      });

      return returnString;
    },
    getStyleObject: function (name) {
      return currentStyle[name];
    },
    showDeveloperConsole: function () {
      developerConsole.showDeveloperConsole();
    }
  };
}());

$.extend(freeboard, jQuery.eventEmitter);
